using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace OpenNETCF.Compression
{
    internal delegate IntPtr alloc_func(IntPtr opaque, uint items, uint size);
    internal delegate void free_func(IntPtr opaque, IntPtr address);
    internal unsafe delegate int Reader(IntPtr state, out byte* buffer);
    internal unsafe delegate int Writer(IntPtr state, byte* buffer, int cb);

    internal class zlib_native
    {

        /* constants */

        public const int Z_NO_FLUSH = 0;
        public const int Z_PARTIAL_FLUSH = 1; /* will be removed, use Z_SYNC_FLUSH instead */
        public const int Z_SYNC_FLUSH = 2;
        public const int Z_FULL_FLUSH = 3;
        public const int Z_FINISH = 4;
        public const int Z_BLOCK = 5;
        /* Allowed flush values; see deflate() and inflate() below for details */

        public const int Z_NO_COMPRESSION = 0;
        public const int Z_BEST_SPEED = 1;
        public const int Z_BEST_COMPRESSION = 9;
        public const int Z_DEFAULT_COMPRESSION = (-1);

        /* Possible values of the data_type field (though see inflate()) */

        public const int Z_DEFLATED = 8;
        /* The deflate compression method (the only one supported in this version) */

        public const int Z_NULL = 0;  /* for initializing zalloc, zfree, opaque */


        /// <summary>
        /// The application can compare zlibVersion and ZLIB_VERSION for consistency.
        /// If the first character differs, the library code actually used is
        /// not compatible with the zlib.h header file used by the application.
        /// This check is automatically made by deflateInit and inflateInit.
        /// </summary>
        /// <returns></returns>
        [DllImport("zlibce")]
        extern static public IntPtr zlibVersion();



        /*
     Initializes the internal stream state for compression. The fields
   zalloc, zfree and opaque must be initialized before by the caller.
   If zalloc and zfree are set to Z_NULL, deflateInit updates them to
   use default allocation functions.

     The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9:
   1 gives best speed, 9 gives best compression, 0 gives no compression at
   all (the input data is simply copied a block at a time).
   Z_DEFAULT_COMPRESSION requests a default compromise between speed and
   compression (currently equivalent to level 6).

     deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not
   enough memory, Z_STREAM_ERROR if level is not a valid compression level,
   Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible
   with the version assumed by the caller (ZLIB_VERSION).
   msg is set to null if there is no error message.  deflateInit does not
   perform any compression: this will be done by deflate().
*/
        static public unsafe ErrorCode deflateInit(ref z_stream strm, int level)
        {
            return deflateInit_(ref strm, level, zlibVersion(), sizeof(z_stream) );
        }

        [DllImport("zlibce", EntryPoint = "deflateInit_")]
        extern static public ErrorCode deflateInit_(ref z_stream strm, int level, IntPtr version, int size);

        [DllImport("zlibce", EntryPoint = "deflateInit2_")]
        extern static public ErrorCode deflateInit2(ref z_stream strm,
                                     CompressionLevel level,
                                     int method,
                                     int windowBits,
                                     int memLevel,
                                     CompressionStrategy strategy, IntPtr version, int size);

        /*
            deflate compresses as much data as possible, and stops when the input
          buffer becomes empty or the output buffer becomes full. It may introduce some
          output latency (reading input without producing any output) except when
          forced to flush.

            The detailed semantics are as follows. deflate performs one or both of the
          following actions:

          - Compress more input starting at next_in and update next_in and avail_in
            accordingly. If not all input can be processed (because there is not
            enough room in the output buffer), next_in and avail_in are updated and
            processing will resume at this point for the next call of deflate().

          - Provide more output starting at next_out and update next_out and avail_out
            accordingly. This action is forced if the parameter flush is non zero.
            Forcing flush frequently degrades the compression ratio, so this parameter
            should be set only when necessary (in interactive applications).
            Some output may be provided even if flush is not set.

          Before the call of deflate(), the application should ensure that at least
          one of the actions is possible, by providing more input and/or consuming
          more output, and updating avail_in or avail_out accordingly; avail_out
          should never be zero before the call. The application can consume the
          compressed output when it wants, for example when the output buffer is full
          (avail_out == 0), or after each call of deflate(). If deflate returns Z_OK
          and with zero avail_out, it must be called again after making room in the
          output buffer because there might be more output pending.

            Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to
          decide how much data to accumualte before producing output, in order to
          maximize compression.

            If the parameter flush is set to Z_SYNC_FLUSH, all pending output is
          flushed to the output buffer and the output is aligned on a byte boundary, so
          that the decompressor can get all input data available so far. (In particular
          avail_in is zero after the call if enough output space has been provided
          before the call.)  Flushing may degrade compression for some compression
          algorithms and so it should be used only when necessary.

            If flush is set to Z_FULL_FLUSH, all output is flushed as with
          Z_SYNC_FLUSH, and the compression state is reset so that decompression can
          restart from this point if previous compressed data has been damaged or if
          random access is desired. Using Z_FULL_FLUSH too often can seriously degrade
          compression.

            If deflate returns with avail_out == 0, this function must be called again
          with the same value of the flush parameter and more output space (updated
          avail_out), until the flush is complete (deflate returns with non-zero
          avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that
          avail_out is greater than six to avoid repeated flush markers due to
          avail_out == 0 on return.

            If the parameter flush is set to Z_FINISH, pending input is processed,
          pending output is flushed and deflate returns with Z_STREAM_END if there
          was enough output space; if deflate returns with Z_OK, this function must be
          called again with Z_FINISH and more output space (updated avail_out) but no
          more input data, until it returns with Z_STREAM_END or an error. After
          deflate has returned Z_STREAM_END, the only possible operations on the
          stream are deflateReset or deflateEnd.

            Z_FINISH can be used immediately after deflateInit if all the compression
          is to be done in a single step. In this case, avail_out must be at least
          the value returned by deflateBound (see below). If deflate does not return
          Z_STREAM_END, then it must be called again as described above.

            deflate() sets strm->adler to the adler32 checksum of all input read
          so far (that is, total_in bytes).

            deflate() may update strm->data_type if it can make a good guess about
          the input data type (Z_BINARY or Z_TEXT). In doubt, the data is considered
          binary. This field is only for information purposes and does not affect
          the compression algorithm in any manner.

            deflate() returns Z_OK if some progress has been made (more input
          processed or more output produced), Z_STREAM_END if all input has been
          consumed and all output has been produced (only when flush is set to
          Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example
          if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible
          (for example avail_in or avail_out was zero). Note that Z_BUF_ERROR is not
          fatal, and deflate() can be called again with more input and more output
          space to continue compressing.
        */

        [DllImport("zlibce")]
        extern static public ErrorCode deflate(ref z_stream strm, int flush);
        /*
             All dynamically allocated data structures for this stream are freed.
           This function discards any unprocessed input and does not flush any
           pending output.

             deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the
           stream state was inconsistent, Z_DATA_ERROR if the stream was freed
           prematurely (some input or output was discarded). In the error case,
           msg may be set but then points to a static string (which must not be
           deallocated).
        */

        [DllImport("zlibce")]
        extern static public int deflateEnd(ref z_stream strm);


        unsafe static public ErrorCode inflateInit(ref z_stream strm)
        {
            return inflateInit_(ref strm, zlibVersion(), sizeof(z_stream));
        }

        [DllImport("zlibce", EntryPoint = "inflateInit_")]
        extern static public ErrorCode inflateInit_(ref z_stream strm, IntPtr version, int size);

        [DllImport("zlibce", EntryPoint = "inflateInit2_")]
        extern static public ErrorCode inflateInit2(ref z_stream strm, int windowBits, IntPtr version, int stream_size);

        /*
            inflate decompresses as much data as possible, and stops when the input
          buffer becomes empty or the output buffer becomes full. It may introduce
          some output latency (reading input without producing any output) except when
          forced to flush.

          The detailed semantics are as follows. inflate performs one or both of the
          following actions:

          - Decompress more input starting at next_in and update next_in and avail_in
            accordingly. If not all input can be processed (because there is not
            enough room in the output buffer), next_in is updated and processing
            will resume at this point for the next call of inflate().

          - Provide more output starting at next_out and update next_out and avail_out
            accordingly.  inflate() provides as much output as possible, until there
            is no more input data or no more space in the output buffer (see below
            about the flush parameter).

          Before the call of inflate(), the application should ensure that at least
          one of the actions is possible, by providing more input and/or consuming
          more output, and updating the next_* and avail_* values accordingly.
          The application can consume the uncompressed output when it wants, for
          example when the output buffer is full (avail_out == 0), or after each
          call of inflate(). If inflate returns Z_OK and with zero avail_out, it
          must be called again after making room in the output buffer because there
          might be more output pending.

            The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH,
          Z_FINISH, or Z_BLOCK. Z_SYNC_FLUSH requests that inflate() flush as much
          output as possible to the output buffer. Z_BLOCK requests that inflate() stop
          if and when it gets to the next deflate block boundary. When decoding the
          zlib or gzip format, this will cause inflate() to return immediately after
          the header and before the first block. When doing a raw inflate, inflate()
          will go ahead and process the first block, and will return when it gets to
          the end of that block, or when it runs out of data.

            The Z_BLOCK option assists in appending to or combining deflate streams.
          Also to assist in this, on return inflate() will set strm->data_type to the
          number of unused bits in the last byte taken from strm->next_in, plus 64
          if inflate() is currently decoding the last block in the deflate stream,
          plus 128 if inflate() returned immediately after decoding an end-of-block
          code or decoding the complete header up to just before the first byte of the
          deflate stream. The end-of-block will not be indicated until all of the
          uncompressed data from that block has been written to strm->next_out.  The
          number of unused bits may in general be greater than seven, except when
          bit 7 of data_type is set, in which case the number of unused bits will be
          less than eight.

            inflate() should normally be called until it returns Z_STREAM_END or an
          error. However if all decompression is to be performed in a single step
          (a single call of inflate), the parameter flush should be set to
          Z_FINISH. In this case all pending input is processed and all pending
          output is flushed; avail_out must be large enough to hold all the
          uncompressed data. (The size of the uncompressed data may have been saved
          by the compressor for this purpose.) The next operation on this stream must
          be inflateEnd to deallocate the decompression state. The use of Z_FINISH
          is never required, but can be used to inform inflate that a faster approach
          may be used for the single inflate() call.

             In this implementation, inflate() always flushes as much output as
          possible to the output buffer, and always uses the faster approach on the
          first call. So the only effect of the flush parameter in this implementation
          is on the return value of inflate(), as noted below, or when it returns early
          because Z_BLOCK is used.

             If a preset dictionary is needed after this call (see inflateSetDictionary
          below), inflate sets strm->adler to the adler32 checksum of the dictionary
          chosen by the compressor and returns Z_NEED_DICT; otherwise it sets
          strm->adler to the adler32 checksum of all output produced so far (that is,
          total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described
          below. At the end of the stream, inflate() checks that its computed adler32
          checksum is equal to that saved by the compressor and returns Z_STREAM_END
          only if the checksum is correct.

            inflate() will decompress and check either zlib-wrapped or gzip-wrapped
          deflate data.  The header type is detected automatically.  Any information
          contained in the gzip header is not retained, so applications that need that
          information should instead use raw inflate, see inflateInit2() below, or
          inflateBack() and perform their own processing of the gzip header and
          trailer.

            inflate() returns Z_OK if some progress has been made (more input processed
          or more output produced), Z_STREAM_END if the end of the compressed data has
          been reached and all uncompressed output has been produced, Z_NEED_DICT if a
          preset dictionary is needed at this point, Z_DATA_ERROR if the input data was
          corrupted (input stream not conforming to the zlib format or incorrect check
          value), Z_STREAM_ERROR if the stream structure was inconsistent (for example
          if next_in or next_out was NULL), Z_MEM_ERROR if there was not enough memory,
          Z_BUF_ERROR if no progress is possible or if there was not enough room in the
          output buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and
          inflate() can be called again with more input and more output space to
          continue decompressing. If Z_DATA_ERROR is returned, the application may then
          call inflateSync() to look for a good compression block if a partial recovery
          of the data is desired.
        */
        [DllImport("zlibce")]
        extern static public ErrorCode inflate(ref z_stream strm, int flush);

        [DllImport("zlibce")]
        extern static public int inflateEnd(ref z_stream strm);

        unsafe static public ErrorCode inflateBackInit(ref z_stream strm, int windowBits)
        {
            byte[] window = new byte[65280 + 2];
            return inflateBackInit_(ref strm, 15, window, zlibVersion(), sizeof(z_stream));
        }

        // define window as follows:
        // byte[] window = new byte[65280 + 2];
        [DllImport("zlibce", EntryPoint="inflateBackInit_")]
        extern static public ErrorCode inflateBackInit_ (ref z_stream strm, int windowBits,
                                         byte[] window,
                                         IntPtr version,
                                         int stream_size);

        /*
             inflateBack() does a raw inflate with a single call using a call-back
           interface for input and output.  This is more efficient than inflate() for
           file i/o applications in that it avoids copying between the output and the
           sliding window by simply making the window itself the output buffer.  This
           function trusts the application to not change the output buffer passed by
           the output function, at least until inflateBack() returns.

             inflateBackInit() must be called first to allocate the internal state
           and to initialize the state with the user-provided window buffer.
           inflateBack() may then be used multiple times to inflate a complete, raw
           deflate stream with each call.  inflateBackEnd() is then called to free
           the allocated state.

             A raw deflate stream is one with no zlib or gzip header or trailer.
           This routine would normally be used in a utility that reads zip or gzip
           files and writes out uncompressed files.  The utility would decode the
           header and process the trailer on its own, hence this routine expects
           only the raw deflate stream to decompress.  This is different from the
           normal behavior of inflate(), which expects either a zlib or gzip header and
           trailer around the deflate stream.

             inflateBack() uses two subroutines supplied by the caller that are then
           called by inflateBack() for input and output.  inflateBack() calls those
           routines until it reads a complete deflate stream and writes out all of the
           uncompressed data, or until it encounters an error.  The function's
           parameters and return types are defined above in the in_func and out_func
           typedefs.  inflateBack() will call in(in_desc, &buf) which should return the
           number of bytes of provided input, and a pointer to that input in buf.  If
           there is no input available, in() must return zero--buf is ignored in that
           case--and inflateBack() will return a buffer error.  inflateBack() will call
           out(out_desc, buf, len) to write the uncompressed data buf[0..len-1].  out()
           should return zero on success, or non-zero on failure.  If out() returns
           non-zero, inflateBack() will return with an error.  Neither in() nor out()
           are permitted to change the contents of the window provided to
           inflateBackInit(), which is also the buffer that out() uses to write from.
           The length written by out() will be at most the window size.  Any non-zero
           amount of input may be provided by in().

             For convenience, inflateBack() can be provided input on the first call by
           setting strm->next_in and strm->avail_in.  If that input is exhausted, then
           in() will be called.  Therefore strm->next_in must be initialized before
           calling inflateBack().  If strm->next_in is Z_NULL, then in() will be called
           immediately for input.  If strm->next_in is not Z_NULL, then strm->avail_in
           must also be initialized, and then if strm->avail_in is not zero, input will
           initially be taken from strm->next_in[0 .. strm->avail_in - 1].

             The in_desc and out_desc parameters of inflateBack() is passed as the
           first parameter of in() and out() respectively when they are called.  These
           descriptors can be optionally used to pass any information that the caller-
           supplied in() and out() functions need to do their job.

             On return, inflateBack() will set strm->next_in and strm->avail_in to
           pass back any unused input that was provided by the last in() call.  The
           return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR
           if in() or out() returned an error, Z_DATA_ERROR if there was a format
           error in the deflate stream (in which case strm->msg is set to indicate the
           nature of the error), or Z_STREAM_ERROR if the stream was not properly
           initialized.  In the case of Z_BUF_ERROR, an input or output error can be
           distinguished using strm->next_in which will be Z_NULL only if in() returned
           an error.  If strm->next is not Z_NULL, then the Z_BUF_ERROR was due to
           out() returning non-zero.  (in() will always be called before out(), so
           strm->next_in is assured to be defined if out() returns non-zero.)  Note
           that inflateBack() cannot return Z_OK.
        */

        [DllImport("zlibce")]
        extern static public ErrorCode inflateBack(ref z_stream strm, Reader inf, IntPtr in_desc, Writer outf, IntPtr out_desc);

        /*
             All memory allocated by inflateBackInit() is freed.

             inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream
           state was inconsistent.
        */
        [DllImport("zlibce")]
        extern static public ErrorCode inflateBackEnd(ref z_stream strm);

        [DllImport("zlibce")]
        extern static public int inflateReset(ref z_stream strm);

        /*
             Compresses the source buffer into the destination buffer.  sourceLen is
           the byte length of the source buffer. Upon entry, destLen is the total
           size of the destination buffer, which must be at least the value returned
           by compressBound(sourceLen). Upon exit, destLen is the actual size of the
           compressed buffer.
             This function can be used to compress a whole file at once if the
           input file is mmap'ed.
             compress returns Z_OK if success, Z_MEM_ERROR if there was not
           enough memory, Z_BUF_ERROR if there was not enough room in the output
           buffer.
        */
        [DllImport("zlibce")]
        extern static public ErrorCode compress(byte[] dest, ref int destLen,
                                 byte[] source, int sourceLen);

        /*
             Compresses the source buffer into the destination buffer. The level
           parameter has the same meaning as in deflateInit.  sourceLen is the byte
           length of the source buffer. Upon entry, destLen is the total size of the
           destination buffer, which must be at least the value returned by
           compressBound(sourceLen). Upon exit, destLen is the actual size of the
           compressed buffer.

             compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
           memory, Z_BUF_ERROR if there was not enough room in the output buffer,
           Z_STREAM_ERROR if the level parameter is invalid.
        */
        [DllImport("zlibce")]
        extern static public int compress2(byte[] dest, ref int destLen,
                                 byte[] source, int sourceLen, int level);


        /*
             Decompresses the source buffer into the destination buffer.  sourceLen is
           the byte length of the source buffer. Upon entry, destLen is the total
           size of the destination buffer, which must be large enough to hold the
           entire uncompressed data. (The size of the uncompressed data must have
           been saved previously by the compressor and transmitted to the decompressor
           by some mechanism outside the scope of this compression library.)
           Upon exit, destLen is the actual size of the compressed buffer.
             This function can be used to decompress a whole file at once if the
           input file is mmap'ed.

             uncompress returns Z_OK if success, Z_MEM_ERROR if there was not
           enough memory, Z_BUF_ERROR if there was not enough room in the output
           buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete.
        */

        [DllImport("zlibce")]
        extern static public ErrorCode uncompress(byte[] dest, ref int destLen,
                                   byte[] source, int sourceLen);


        [DllImport("zlibce")]
        extern static public int gzopen(string path, string mode);
        /*
             Opens a gzip (.gz) file for reading or writing. The mode parameter
           is as in fopen ("rb" or "wb") but can also include a compression level
           ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for
           Huffman only compression as in "wb1h", or 'R' for run-length encoding
           as in "wb1R". (See the description of deflateInit2 for more information
           about the strategy parameter.)

             gzopen can be used to read a file which is not in gzip format; in this
           case gzread will directly read from the file without decompression.

             gzopen returns NULL if the file could not be opened or if there was
           insufficient memory to allocate the (de)compression state; errno
           can be checked to distinguish the two cases (if errno is zero, the
           zlib error is Z_MEM_ERROR).  */


        [DllImport("zlibce")]
        extern static public int gzread(int file, IntPtr buf, int len);

        [DllImport("zlibce")]
        extern static public int gzwrite(int file, IntPtr buf, int len);

        [DllImport("zlibce")]
        extern static public int gztell(int file);

        [DllImport("zlibce")]
        extern static public int gzseek(int file, int offset, int whence);

        [DllImport("zlibce")]
        extern static public int gzclose(int file);

        [DllImport("zlibce")]
        extern static public int crc32(int crc, byte[] buf, int len);

        [DllImport("zlibce")]
        extern static public int crc32(int crc, IntPtr buf, int len);

        [DllImport("zlibce")]
        extern static public IntPtr zlibVesrion();

        [DllImport("coredll")]
        extern static public void memcpy(IntPtr p1, IntPtr p2, int cb);
        [DllImport("coredll")]
        unsafe extern static public void memcpy(byte* p1, byte* p2, int cb);

        public static ErrorCode Inflate2(Writer writer, Reader reader)
        {
            z_stream s = new z_stream();
            ErrorCode ret = zlib_native.inflateBackInit(ref s, 15);
            if (ret == ErrorCode.ErrNo)
                return ret;

            ret = inflateBack(ref s, reader, IntPtr.Zero, writer, IntPtr.Zero);
            if (ret == ErrorCode.ErrNo)
                return ret;

            ret = inflateBackEnd(ref s);

            return ret;
        }
    }
    /*
    internal enum ZContent
    {
        Z_BINARY = 0,
        Z_TEXT = 1,
        Z_ASCII = Z_TEXT,   // for compatibility with 1.2.2 and earlier 
        Z_UNKNOWN = 2,
    }
    */
}
